/*
 * $Id: InetAddress.cpp 130 2011-10-06 08:46:08Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/net/InetAddress.hpp>
#include <xp/net/Socket.hpp>
#include <xp/bas/memory.h>
#include <xp/bas/locale.h>
#include <xp/bas/assert.h>
#include <xp/bas/endian.h>
#include <xp/bas/string.h>
#include <xp/net/inet.h>

XP_BEGIN_NAMESPACE2 (xp, net)

using namespace xp::bas;

InetAddress::InetAddress ()
{
	xp_memset (&this->address_storage, 0, XP_SIZEOF(this->address_storage));
	this->address_storage.ss_family = V0;
}

InetAddress::InetAddress (int family) 
{
	xp_memset (&this->address_storage, 0, XP_SIZEOF(this->address_storage));
	this->address_storage.ss_family = family;
}

InetAddress::InetAddress (const struct sockaddr_storage& adr)
{
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(this->address_storage));
}

InetAddress::InetAddress (const struct sockaddr_in& adr)
{
	XP_ASSERT (XP_SIZEOF(this->address_storage) >= XP_SIZEOF(adr));
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(adr));
}

InetAddress::InetAddress (const struct sockaddr_in6& adr)
{
	XP_ASSERT (XP_SIZEOF(this->address_storage) >= XP_SIZEOF(adr));
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(adr));
}

InetAddress::InetAddress (const InetAddress& adr)
{
	xp_memcpy (&this->address_storage,
		&adr.address_storage, XP_SIZEOF(this->address_storage));
}

InetAddress& InetAddress::operator= (const InetAddress& adr)
{
	xp_memcpy (&this->address_storage, 
		&adr.address_storage, XP_SIZEOF(this->address_storage));
	return *this;
}

InetAddress& InetAddress::operator= (const struct sockaddr_storage& adr)
{
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(this->address_storage));
	return *this;
}

InetAddress& InetAddress::operator= (const struct sockaddr_in& adr)
{
	XP_ASSERT (XP_SIZEOF(this->address_storage) >= XP_SIZEOF(adr));
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(adr));
	return *this;
}

InetAddress& InetAddress::operator= (const struct sockaddr_in6& adr)
{
	XP_ASSERT (XP_SIZEOF(this->address_storage) >= XP_SIZEOF(adr));
	xp_memcpy (&this->address_storage, &adr, XP_SIZEOF(adr));
	return *this;
}

bool InetAddress::operator== (const InetAddress& adr) const
{
	//xp_size_t ss = this->storageSize();
	//if (ss != adr.storageSize()) return false;
	//return xp_memcmp (this->storage(), adr.storage(), ss) == 0;
	return hasEqualFamily(adr) && hasEqualHost(adr) && hasEqualService(adr);
}

bool InetAddress::operator!= (const InetAddress& adr) const
{
	//xp_size_t ss = this->storageSize();
	//if (ss != adr.storageSize()) return true;
	//xp_memcmp(this->storage(), adr.storage(), ss));
	//return xp_memcmp(this->storage(), adr.storage(), ss) != 0;
	return !hasEqualFamily(adr) || !hasEqualHost(adr) || !hasEqualService(adr);
}

bool InetAddress::hasEqualFamily (const InetAddress& addr) const
{
	return this->family() == addr.family();
}

bool InetAddress::hasEqualHost (const InetAddress& addr) const
{
	if (this->family() != addr.family()) return false;

	if (this->family() == V4) 
	{
		struct sockaddr_in* p1 = 
			(struct sockaddr_in*)&this->address_storage;
		struct sockaddr_in* p2 = 
			(struct sockaddr_in*)&addr.address_storage;

		return xp_memcmp(&p1->sin_addr, 
			&p2->sin_addr, XP_SIZEOF(p1->sin_addr)) == 0;
	}

	if (this->family() == V6) 
	{
		struct sockaddr_in6* p1 = 
			(struct sockaddr_in6*)&this->address_storage;
		struct sockaddr_in6* p2 = 
			(struct sockaddr_in6*)&addr.address_storage;

		return xp_memcmp(&p1->sin6_addr, 
			&p2->sin6_addr, XP_SIZEOF(p1->sin6_addr)) == 0;
	}

	return false;
}

bool InetAddress::hasEqualService (const InetAddress& addr) const
{
	if (this->family() != addr.family()) return false;

	if (this->family() == V4) {
	
		struct sockaddr_in* p1 = 
			(struct sockaddr_in*)&this->address_storage;
		struct sockaddr_in* p2 = 
			(struct sockaddr_in*)&addr.address_storage;

		return xp_memcmp(&p1->sin_port, 
			&p2->sin_port, XP_SIZEOF(p1->sin_port)) == 0;
	}

	if (this->family() == V6) 
	{

		struct sockaddr_in6* p1 = 
			(struct sockaddr_in6*)&this->address_storage;
		struct sockaddr_in6* p2 = 
			(struct sockaddr_in6*)&addr.address_storage;

		return xp_memcmp(&p1->sin6_port, 
			&p2->sin6_port, XP_SIZEOF(p1->sin6_port)) == 0;
	}

	return false;
}


Address::len_t InetAddress::storageSize () const
{
	if (this->family() == V4) {
		XP_ASSERT (
			XP_SIZEOF(this->address_storage) >= 
			XP_SIZEOF(struct sockaddr_in));
		return (len_t)XP_SIZEOF(struct sockaddr_in);
	}

	if (this->family() == V6) {
		XP_ASSERT (
			XP_SIZEOF(this->address_storage) >= 
			XP_SIZEOF(struct sockaddr_in6));
		return (len_t)XP_SIZEOF(struct sockaddr_in6);
	}

	return (len_t)XP_SIZEOF(this->address_storage);
}

bool InetAddress::isLoopBack () const 
{
	if (this->family() == V4) {
		struct sockaddr_in* v4 = 
			(struct sockaddr_in*)&this->address_storage;
		return v4->sin_addr.s_addr == xp_hton32(INADDR_LOOPBACK);
	}

	if (this->family() == V6) {
		struct sockaddr_in6* v6 = 
			(struct sockaddr_in6*)&this->address_storage;
		return xp_memcmp (&v6->sin6_addr, 
			&in6addr_loopback, XP_SIZEOF(in6addr_loopback)) == 0;
	}

	return false;
}

bool InetAddress::isAnyLocal () const 
{
	if (this->family() == V4) {
		struct sockaddr_in* v4 = 
			(struct sockaddr_in*)&this->address_storage;
		return v4->sin_addr.s_addr == xp_hton32(INADDR_ANY);
	}

	if (this->family() == V6) {
		struct sockaddr_in6* v6 = 
			(struct sockaddr_in6*)&this->address_storage;
		return xp_memcmp (&v6->sin6_addr, 
			&in6addr_any, XP_SIZEOF(in6addr_any)) == 0;
	}

	return false;
}

xp_uint16_t InetAddress::port () const
{
	if (this->family() == V4) {
		struct sockaddr_in* v4 = 
			(struct sockaddr_in*)&this->address_storage;
		return v4->sin_port;
	}

	if (this->family() == V6) {
		struct sockaddr_in6* v6 = 
			(struct sockaddr_in6*)&this->address_storage;
		return v6->sin6_port;
	}

	return 0;
}

void InetAddress::setPort (xp_uint16_t v) 
{
	if (this->family() == V4) {
		struct sockaddr_in* v4 = 
			(struct sockaddr_in*)&this->address_storage;
		v4->sin_port = v;
	}

	if (this->family() == V6) {
		struct sockaddr_in6* v6 = 
			(struct sockaddr_in6*)&this->address_storage;
		v6->sin6_port = v;
	}
}

int InetAddress::getNumericHost (xp_char_t* buf, xp_size_t size) const
{
	xp_sckadr_t* sa = this->storage();
	xp_scklen_t len = this->storageSize();
	
#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
	sa->sa_len = len;
#endif

#if defined(XP_CHAR_IS_MCHAR)
	return (::getnameinfo (sa, len,
		buf, size, XP_NULL, 0, NI_NUMERICHOST) == 0)? 0: -1;
#else
	xp_mchar_t mb_buf[MAX_HOST_LEN + 1];
	xp_size_t n;
	
	if (::getnameinfo (
		sa, len, mb_buf, XP_COUNTOF(mb_buf), 
		XP_NULL, 0, NI_NUMERICHOST) != 0) return -1;
	n = xp_mcstowcs (mb_buf, buf, size);
	if (n == (xp_size_t)-1 || n == 0) return -1;
	return 0;
#endif
}

int InetAddress::getNumericService (
	xp_char_t* buf, xp_size_t size) const
{
	xp_sckadr_t* sa = this->storage();
	xp_scklen_t len = this->storageSize();
	
#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
	sa->sa_len = len;
#endif

#if defined(XP_CHAR_IS_MCHAR)
	return (::getnameinfo (sa, len,
		XP_NULL, 0, buf, size, NI_NUMERICSERV) == 0)? 0: -1;
#else
	xp_mchar_t mb_buf[MAX_SERVICE_LEN + 1];
	xp_size_t n;

	if (::getnameinfo (
		sa, len, XP_NULL, 0,
		mb_buf, XP_COUNTOF(mb_buf), NI_NUMERICSERV) != 0) return -1;
	n = xp_mcstowcs (mb_buf, buf, size);
	if (n == (xp_size_t)-1 || n == 0) return -1;
	return 0;
#endif
}

int InetAddress::getHostName (xp_char_t* buf, xp_size_t size) const
{
	xp_sckadr_t* sa = this->storage();
	xp_scklen_t len = this->storageSize();
	
#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
	sa->sa_len = len;
#endif

#if defined(XP_CHAR_IS_MCHAR)
	return (::getnameinfo (sa, len,
		buf, size, XP_NULL, 0, 0) == 0)? 0: -1;
#else
	xp_mchar_t mb_buf[MAX_HOST_LEN + 1];
	xp_size_t n;

	if (::getnameinfo (sa, len, 
		mb_buf, XP_COUNTOF(mb_buf), 
		XP_NULL, 0, 0) != 0) return -1;

	n = xp_mcstowcs (mb_buf, buf, size);
	if (n == (xp_size_t)-1 || n == 0) return -1;
	return 0;
#endif
}

int InetAddress::getServiceName (xp_char_t* buf, xp_size_t size) const
{
	xp_sckadr_t* sa = this->storage();
	xp_scklen_t len = this->storageSize();

#if !defined(__linux) && !defined(__sun) && !defined(_WIN32)
	sa->sa_len = len;
#endif

#if defined(XP_CHAR_IS_MCHAR)
	// TODO: check the behavior of getnameinfo in other OSes
	return (::getnameinfo (sa, len, XP_NULL, 0, buf, size, 0) == 0)? 0: -1;
#else
	xp_mchar_t mb_buf[MAX_SERVICE_LEN + 1];
	xp_size_t n;
	
	// NOTICE:
	//   getnameinfo in _WIN32 fails if the service name 
	//   can not be resolved when NI_NUMERICSERV is not specified.
	if (::getnameinfo (sa, len, XP_NULL, 0,
		mb_buf, XP_COUNTOF(mb_buf), 0) != 0) {
	#ifdef _WIN32
		if (::getnameinfo (sa, len,
			XP_NULL, 0, mb_buf, XP_COUNTOF(mb_buf), 
			NI_NUMERICSERV) != 0) return -1;
	#else
		return -1;
	#endif
	}

	n = xp_mcstowcs (mb_buf, buf, size);
	if (n == (xp_size_t)-1 || n == 0) return -1;
	return 0;
#endif
}

xp_char_t* InetAddress::numericHostStr (xp_char_t* buf, xp_size_t len) const
{
	if (len > 0 && this->getNumericHost(buf, len) <= -1) buf[0] = XP_T('\0');
	return buf;
}

xp_char_t* InetAddress::numericServiceStr (xp_char_t* buf, xp_size_t len) const
{
	if (len > 0 && this->getNumericService(buf, len) <= -1) buf[0] = XP_T('\0');
	return buf;
}

String InetAddress::numericHost () const
{
	xp_char_t buf[MAX_HOST_LEN + 1];
	if (this->getNumericHost (buf, XP_COUNTOF(buf)) <= -1) return String();
	return String(buf);
}	

String InetAddress::numericService () const
{
	xp_char_t buf[MAX_SERVICE_LEN + 1];
	if (this->getNumericService(buf, XP_COUNTOF(buf)) <= -1) return String();
	return String(buf);
}	

String InetAddress::hostName () const
{
	xp_char_t buf[MAX_HOST_LEN + 1];
	if (getHostName (buf, XP_COUNTOF(buf)) <= -1) return String();
	return String(buf);
}	

String InetAddress::serviceName () const
{
	xp_char_t buf[MAX_SERVICE_LEN + 1];
	if (getServiceName (buf, XP_COUNTOF(buf)) <= -1) return String();
	return String(buf);
}	

xp_char_t* InetAddress::numericStr (xp_char_t* buf, xp_size_t len) const
{
	xp_size_t pos = 0;
	xp_size_t rem = len;

	if (len > 0)
	{
		xp_size_t tmp;

		if (this->family() == V6) 
		{
			if (rem <= 1) goto oops;

			buf[pos++] = XP_T('[');
			rem--;
			buf[pos] = XP_T('\0');
		}

		if (rem <= 1) goto oops;
		if (this->getNumericHost (&buf[pos], rem) <= -1)  
		{
			pos = 0;
			goto oops;
		}

		tmp = xp_strlen(&buf[pos]);
		pos += tmp;
		rem -= tmp;

		if (this->family() == V6) 
		{
			if (rem <= 1) goto oops;
			buf[pos++] = XP_T(']');
			rem--;
			buf[pos] = XP_T('\0');
		}

		if (rem <= 1) goto oops;
		buf[pos++] = XP_T(':');
		rem--;
		buf[pos] = XP_T('\0');

		if (rem <= 1) goto oops;
		if (this->getNumericService(&buf[pos],rem) <= -1)
		{
			pos = 0;
			goto oops;
		}
	}

	return buf;

oops:
	buf[pos] = XP_T('\0');
	return buf;
}

String InetAddress::numericString () const
{
	xp_char_t buf[MAX_HOST_LEN + MAX_SERVICE_LEN + 10];
	return String (numericStr (buf, XP_COUNTOF(buf)));
}

int InetAddress::resolve (
	const xp_char_t* service, InetAddress* address) 
{
	int errnum;
	return address->resolve_address (
		service, XP_NULL, address->family(), address->type(), &errnum);
}

int InetAddress::resolve (
	const xp_char_t* service, const xp_char_t* host, 
	InetAddress* address) 
{
	int errnum;
	return address->resolve_address (
			service, host, address->family(), address->type(), &errnum);
}

/* 
 * NOTICE: 
 *   When host is "", the address is resolved to localhost.
 *   When host is XP_NULL, the address is resolved to INADDR_ANY 
 *   or IN6ADDR_ANY_INIT depending on the address family.
 */
int InetAddress::resolve_address (
	const xp_char_t* service, const xp_char_t* host, 
	int family, int type, int* errnum)
{
	struct addrinfo hints;
	struct addrinfo* info, * p;

	XP_ASSERT (family == V0 || family == V4 || family == V6);
	XP_ASSERT (errnum != XP_NULL);

	xp_memset (&hints, 0, XP_SIZEOF(hints));
	hints.ai_family = family;
	hints.ai_socktype = type;
	if (host == XP_NULL) hints.ai_flags = AI_PASSIVE; 
	else if (host[0] == XP_CHAR('\0')) host = XP_NULL;

#if defined(XP_CHAR_IS_MCHAR)
	*errnum = ::getaddrinfo (host, service, &hints, &info);
#else
	xp_mchar_t mb_host[MAX_HOST_LEN + 1];
	xp_mchar_t mb_service[MAX_SERVICE_LEN + 1];
	xp_mchar_t* p_host = XP_NULL;
	xp_mchar_t* p_service = XP_NULL;

	if (host) {
		if (xp_wcstomcs (host, mb_host, XP_COUNTOF(mb_host)) == 0) 
		{
			*errnum = EAI_NONAME;
			return -1;
		}
		p_host = mb_host;
	}
	if (service) 
	{
		if (xp_wcstomcs (service, mb_service, XP_COUNTOF(mb_service)) == 0) 
		{
			*errnum = EAI_NONAME;
			return -1;
		}
		p_service = mb_service;
	}
	*errnum = ::getaddrinfo (p_host, p_service, &hints, &info);	
#endif
	if (*errnum != 0) return -1;
	
	for (p = info; p != XP_NULL; p = p->ai_next) 
	{
		if (family != V0 && p->ai_family != family) continue;
		if (type != 0 && p->ai_socktype != type) continue;
		if (XP_SIZEOF(this->address_storage) < p->ai_addrlen) continue;

		xp_memcpy (&this->address_storage, p->ai_addr, p->ai_addrlen);
		break;
	}
	::freeaddrinfo (info);
	return 0;
}

int InetAddress::fromString (const xp_char_t* str, InetAddress* address)
{
	return InetAddress::fromString (str, xp_strlen(str), address);
}

int InetAddress::fromString (const xp_char_t* str, xp_size_t len, InetAddress* address)
{
	const xp_char_t* p;
	const xp_char_t* end;
	xp_cstr_t tmp;

	int type;
	union 
	{
		struct sockaddr_storage dummy;
		struct sockaddr_in in4;
		struct sockaddr_in6 in6;
	} tmpad;

	xp_memset (&tmpad, 0, XP_SIZEOF(tmpad));

	p = str;
	end = str + len;

	if (p >= end) return -1;

	if (*p == XP_T('['))
	{
		/* IPv6 address */
		tmp.ptr = ++p; /* skip [ and remember the position */
		while (p < end && *p != XP_T('%') && *p != XP_T(']')) p++;

		if (p >= end) return -1;

		tmp.len = p - tmp.ptr;
		if (*p == XP_T('%'))
		{
			/* handle scope id */
			xp_uint32_t x;

			p++; /* skip % */

			if (p >= end)
			{
				/* premature end */
				return -1;
			}

			if (*p >= XP_T('0') && *p <= XP_T('9')) 
			{
				/* numeric scope id */
				tmpad.in6.sin6_scope_id = 0;
				do
				{
					x = tmpad.in6.sin6_scope_id * 10 + (*p - XP_T('0'));
					if (x < tmpad.in6.sin6_scope_id) return -1; /* overflow */
					tmpad.in6.sin6_scope_id = x;
					p++;
				}
				while (p < end && *p >= XP_T('0') && *p <= XP_T('9'));
			}
#if 0
TODO: support interface name
			else
			{
				/* interface name as a scope id? */
				const xp_char_t* stmp = p;
				unsigned int index;
				do p++; while (p < end && *p != XP_T(']'));
				if (xp_nwifmbsntoindex (stmp, p - stmp, &index) <= -1) return -1;
				tmpad.in6.sin6_scope_id = index;
			}
#endif

			if (p >= end || *p != XP_T(']')) return -1;
		}
		p++; /* skip ] */

		if (xp_strxtoip6addr (tmp.ptr, tmp.len, (xp_ip6addr_t*)&tmpad.in6.sin6_addr) <= -1) return -1;
		type = V6;
	}
	else
	{
		/* host name or IPv4 address */
		tmp.ptr = p;
		while (p < end && *p != XP_T(':')) p++;
		tmp.len = p - tmp.ptr;

		if (xp_strxtoip4addr (tmp.ptr, tmp.len, (xp_ip4addr_t*)&tmpad.in4.sin_addr) <= -1)
		{
			if (p >= end || *p != XP_T(':')) return -1;
			
			/* check if it is an IPv6 address not enclosed in []. 
			 * the port number can't be specified in this format. */

			while (p < end && *p != XP_T('%')) p++;
			tmp.len = p - tmp.ptr;

			if (xp_strxtoip6addr (tmp.ptr, tmp.len, (xp_ip6addr_t*)&tmpad.in6.sin6_addr) <= -1) 
				return -1;

			if (p < end && *p == XP_T('%'))
			{
				/* handle scope id */
				xp_uint32_t x;

				p++; /* skip % */

				if (p >= end)
				{
					/* premature end */
					return -1;
				}

				if (*p >= XP_T('0') && *p <= XP_T('9')) 
				{
					/* numeric scope id */
					tmpad.in6.sin6_scope_id = 0;
					do
					{
						x = tmpad.in6.sin6_scope_id * 10 + (*p - XP_T('0'));
						if (x < tmpad.in6.sin6_scope_id) return -1; /* overflow */
						tmpad.in6.sin6_scope_id = x;
						p++;
					}
					while (p < end && *p >= XP_T('0') && *p <= XP_T('9'));
				}
#if 0
TODO: support a interface name as a scope id
				else
				{
					/* interface name as a scope id? */
					const xp_char_t* stmp = p;
					unsigned int index;
					do p++; while (p < end);
					if (xp_nwifmbsntoindex (stmp, p - stmp, &index) <= -1) return -1;
					tmpad.in6.sin6_scope_id = index;
				}
#endif
			}

			if (p < end) return -1;

			type = V6;
			goto done;
		}

		type = V4;
	}

	if (p < end && *p == XP_T(':')) 
	{
		/* port number */
		xp_uint32_t port = 0;

		p++; /* skip : */

		tmp.ptr = p;
		while (p < end && *p >= XP_T('0') && *p <= XP_T('9'))
		{
			port = port * 10 + (*p - XP_T('0'));
			p++;
		}

		tmp.len = p - tmp.ptr;
		if (tmp.len <= 0 || tmp.len >= 6 || 
		    port > XP_TYPE_MAX(xp_uint16_t)) return -1;

		if (type == V4)
			tmpad.in4.sin_port = xp_hton16 (port);
		else
			tmpad.in6.sin6_port = xp_hton16 (port);
	}

done:
	if (address) 
	{
		xp_memcpy (&address->address_storage, &tmpad, XP_SIZEOF(address->address_storage));
		address->address_storage.ss_family = type;
	}

	return 0;
}

XP_END_NAMESPACE2 (xp, net)
